雖然我也很想趕快發個感想文結業,但是題目講不完只好再寫一篇。這一篇想來示範所謂的新的角度看程式的一個例子,就是不需要封裝與繼承,還是可以有多型這回事。
多形 (polymorphism) 在學術上,分為參數多型 (parametric polymorphism) 及特設多型 (ad-hoc polymorphism)。參數多型適用於靜態型別的語言,例如 Haskell、OCaml 等等,物件導向的 Java、C# 則是透過泛型來實作。因為 Elixir 是動態型別語言,所以基本上不適用。
而特設多型還分成 close ad-hoc polymorphism 及 open ad-hoc polymorphism。前者在 Elixir 中,就是函式的 pattern matching。而 open ad-hoc polymorphism,就是今天所要講的 Protocol。
多型具體講起來,就是依資料型別 (物件型別) 的不同,會有不一樣的行為。回想一下 Enum
模組,像是 Enum.map/2
、Enum.reduce/3
,第一個參數可以使用各種型別的集合,這些函式都能正確的遍歷每個集合。這就是使用了 Protocol 機制達成的。
例如我們想要實作一個 Find.next/1
函式,會依不同的型別找到下一個值,首先要先宣告一個 Protocol:
defprotocol Find do
def next(data)
end
defprotocol
後面接模組名稱,並在 do...end
區塊裡放入這個 protocol 所擁有的函式名稱及參數,這些函式沒有本體。如果你會寫 Java 的話,可以想像它有點像是 Interface。
而不同型別要實作此 protocol 時,要使用 defimpl
:
defimpl Find, for: Integer do
def next(i), do: i + 1
end
defimpl Find, for: BitString do
def next(str) do
str
|> String.to_charlist
|> Enum.map(&(&1 + 1))
|> List.to_string
end
end
這些 defimpl
若是內建型別,習慣上會放在跟 defprotocol
同一個檔案裡。而若是自訂的 struct 的話,則會放在宣告 struct 的檔案下。
這麼一來,Find.next/1
就會依傳入的型別不同,進行不同的動作了:
iex> Find.next(1)
2
iex> Find.next("a")
"b"
iex> Find.next("abc")
"bcd"
如果想要實作對其它所有的型別 protocol 的話,可以使用 For: Any
:
defimpl Next, for: Any do
def next(_), do: :error
end
下一篇就是感想文了。Happy hacking!